﻿// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动！任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任！

using Admin.NET.Application.Entity;
using SqlSugar;
using System.ComponentModel;
using Admin.NET.Core.Service;
using Microsoft.Extensions.Logging;
namespace Admin.NET.Application;

/// <summary>
/// 计划延迟服务 🧩
/// </summary>
[ApiDescriptionSettings(ApplicationConst.GroupName, Order = 100)]
public partial class PlanDelayViewsService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<PlanList> _planListRep;
    private readonly SqlSugarRepository<SysUser> _sysUserRep;
    private readonly SqlSugarRepository<SysUserExtOrg> _sysUserExtOrgRep;
    private readonly ISqlSugarClient _sqlSugarClient;
    private readonly SysMessageService _sysMessageService;
    private readonly SysNoticeService _sysNoticeService;
    private readonly ILogger<PlanDelayViewsService> _logger;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="planListRep"></param>
    /// <param name="sqlSugarClient"></param>
    /// <param name="sysUserRep"></param>
    /// <param name="sysUserExtOrgRep"></param>
    /// <param name="sysMessageService"></param>
    /// <param name="sysNoticeService"></param>
    /// <param name="logger">日志记录器</param>
    public PlanDelayViewsService(
        SqlSugarRepository<PlanList> planListRep,
        ISqlSugarClient sqlSugarClient,
        SqlSugarRepository<SysUser> sysUserRep,
        SqlSugarRepository<SysUserExtOrg> sysUserExtOrgRep,
        SysMessageService sysMessageService,
        SysNoticeService sysNoticeService,
        ILogger<PlanDelayViewsService> logger)
    {
        _planListRep = planListRep;
        _sqlSugarClient = sqlSugarClient;
        _sysUserRep = sysUserRep;
        _sysUserExtOrgRep = sysUserExtOrgRep;
        _sysMessageService = sysMessageService;
        _sysNoticeService = sysNoticeService;
        _logger = logger;
    }

    /// <summary>
    /// 通用下拉项
    /// </summary>
    public class OptionDto
    {
        public long? Value { get; set; }
        public string Label { get; set; } = string.Empty;
    }

    #region 全局筛选数据源
    private ISugarQueryable<PlanList> GetGlobalFilterDataSource(PlanDelayAllInput input)
    {
        // 月份
        input.workTime = input.workTime?.Trim();

        // 构建查询
        var query = _planListRep.AsQueryable();

        // 月份筛选 - 修正逻辑：项目的PlanStart或PlanEnd在月份范围内即包含
        var (startDate, endDate) = DateTimeUtil.CalculateWorkTimeRange(input.workTime);
        
        // 筛选条件：PlanStart或PlanEnd在月份范围内
        query = query.Where(u => 
            (u.PlanStart >= startDate && u.PlanStart <= endDate) ||
            (u.PlanEnd >= startDate && u.PlanEnd <= endDate));

        // 应用筛选条件
        query = query.WhereIF(input.ClassName != null, u => u.ClassName == input.ClassName);
        query = query.WhereIF(input.WorkAreaId != null, u => u.WorkAreaId == input.WorkAreaId);

        return query;
    }
    #endregion

    #region 获取计划已开未开状态记录
    private ISugarQueryable<PlanList> GetPlanStartDelayList(PlanDelayStartInput input)
    {
        var query = GetGlobalFilterDataSource(input);
        // 判断PlanStart状态
        // 如果PlanStart是true,那么筛选出所有已开始计划
        // 如果PlanStart是false,那么筛选出所有未开始计划
        query = query.WhereIF(input.PlanStart == true, u => u.ActualStart.HasValue);
        query = query.WhereIF(input.PlanStart == false, u => !u.ActualStart.HasValue);
        return query;
    }
    private ISugarQueryable<PlanList> GetPlanEndDelayList(PlanDelayStartInput input)
    {
        var query = GetGlobalFilterDataSource(input);
        // 判断PlanStart状态
        // 如果PlanStart是true,那么筛选出所有已完工计划
        // 如果PlanStart是false,那么筛选出所有未完工计划
        query = query.WhereIF(input.PlanStart == true, u => u.ActualEnd.HasValue);
        query = query.WhereIF(input.PlanStart == false, u => !u.ActualEnd.HasValue);
        return query;
    }
    #endregion

    #region 获取总分析卡片模块
    [DisplayName("数据分析模块")]
    [ApiDescriptionSettings(Name = "GetPlanDelayStatus"), HttpPost]
    public async Task<PlanDelayStatusOutput> GetPlanDelayStatus(PlanDelayStartInput input)
    {
        // 创建输出对象
        PlanDelayStatusOutput pDSO = new PlanDelayStatusOutput();
        
        // 获取计划项数据源
        var query = GetGlobalFilterDataSource(input);
        var all = query.Count();
        
        // 计算截止日期
        var cutoffDate = GetCutoffDate(input.workTime);
        
        // 获取所有计划数据并计算延期
        var allPlans = await query
            .Select(u => new
            {
                Id = u.Id,
                PlanStart = u.PlanStart,
                PlanEnd = u.PlanEnd,
                ActualStart = u.ActualStart,
                ActualEnd = u.ActualEnd,
                // 计算开工延期天数
                StartDelay = u.ActualStart.HasValue 
                    ? SqlFunc.DateDiff(DateType.Day, u.PlanStart.Value, u.ActualStart.Value)
                    : SqlFunc.DateDiff(DateType.Day, u.PlanStart.Value, cutoffDate),
                // 计算完工延期天数  
                EndDelay = u.ActualEnd.HasValue 
                    ? SqlFunc.DateDiff(DateType.Day, u.PlanEnd.Value, u.ActualEnd.Value)
                    : SqlFunc.DateDiff(DateType.Day, u.PlanEnd.Value, cutoffDate)
            })
            .ToListAsync();
            
        // 获取月份范围
        var (startDate, endDate) = DateTimeUtil.CalculateWorkTimeRange(input.workTime);
        
        // 基础统计 - 按正确规则
        // 计划开工数量：筛选月份内计划开工的项目
        var planStartInMonth = allPlans.Where(x => x.PlanStart.HasValue && x.PlanStart >= startDate && x.PlanStart <= endDate).ToList();
        // 实际开工数量：在计划开工范围内，已经有实际开工日期的项目
        var actualStartInMonth = planStartInMonth.Where(x => x.ActualStart.HasValue).ToList();
        
        // 计划完工数量：筛选月份内计划完工的项目  
        var planEndInMonth = allPlans.Where(x => x.PlanEnd.HasValue && x.PlanEnd >= startDate && x.PlanEnd <= endDate).ToList();
        // 实际完工数量：在计划完工范围内，已经有实际完工日期的项目
        var actualEndInMonth = planEndInMonth.Where(x => x.ActualEnd.HasValue).ToList();
        
        pDSO.AllPlanCount = planStartInMonth.Count(); // 筛选月份内计划开工数量
        pDSO.AllActualCount = actualStartInMonth.Count(); // 筛选月份内实际开工数量
        pDSO.AllPlanEndCount = planEndInMonth.Count(); // 筛选月份内计划完工数量
        pDSO.AllActualEndCount = actualEndInMonth.Count(); // 筛选月份内实际完工数量
        
        // 到时间未开工统计：筛选月份内 PlanStart ≤ 截止日期且ActualStart为空 / 筛选月份内 PlanStart ≤ 截止日期
        var shouldStartPlans = planStartInMonth.Where(x => x.PlanStart <= cutoffDate).ToList();
        var notStartedPlans = shouldStartPlans.Where(x => !x.ActualStart.HasValue).ToList();
        
        // 到时间未完工统计：筛选月份内 PlanEnd ≤ 截止日期且ActualEnd为空 / 筛选月份内 PlanEnd ≤ 截止日期
        var shouldEndPlans = planEndInMonth.Where(x => x.PlanEnd <= cutoffDate).ToList();
        var notFinishedPlans = shouldEndPlans.Where(x => !x.ActualEnd.HasValue).ToList();
        
        pDSO.DelayPlanCount = notStartedPlans.Count();
        pDSO.DelayActualCount = notFinishedPlans.Count();
        
        // 计算到时间的总数
        var shouldStartedCount = shouldStartPlans.Count();
        var shouldFinishedCount = shouldEndPlans.Count();
        
        pDSO.ShouldStartedCount = shouldStartedCount;
        pDSO.ShouldFinishedCount = shouldFinishedCount;
        
        // 计算延期率：到时间未开工 / 到时间应开工总数，到时间未完工 / 到时间应完工总数
        pDSO.DelayPlanRate = shouldStartedCount > 0 
            ? Math.Round((decimal)pDSO.DelayPlanCount / shouldStartedCount * 100, 2) 
            : 0;
        pDSO.DelayActualRate = shouldFinishedCount > 0 
            ? Math.Round((decimal)pDSO.DelayActualCount / shouldFinishedCount * 100, 2) 
            : 0;

        return pDSO;
    }
    #endregion

    #region 获取延期情况 用于 前端Echarts绘制

    /// <summary>
    /// 各区域延期情况统计
    /// </summary>
    [DisplayName("获取各区域延期情况")]
    [ApiDescriptionSettings(Name = "GetAreaDelayCharts"), HttpPost]
    public async Task<PlanDelayChartsOutput> GetAreaDelayCharts(PlanDelayAllInput input)
    {
        var result = new PlanDelayChartsOutput();
        
        // 计算截止日期
        var cutoffDate = GetCutoffDate(input.workTime);
        
        // 获取所有数据到内存
        var allList = await GetGlobalFilterDataSource(input)
            .LeftJoin<SysOrg>((u, org) => u.WorkAreaId == org.Id)
            .Select((u, org) => new
            {
                AreaName = org.Name ?? "未知区域",
                PlanStart = u.PlanStart,
                ActualStart = u.ActualStart,
                PlanEnd = u.PlanEnd,
                ActualEnd = u.ActualEnd,
                WorkAreaId = u.WorkAreaId,
                Id = u.Id
            })
            .ToListAsync();

        var areaStats = allList
            .GroupBy(x => x.AreaName)
            .Select(g => new
            {
                AreaName = g.Key,
                TotalCount = g.Count(),
                StartDelayCount = g.Count(x =>
                    (x.ActualStart.HasValue && (x.ActualStart.Value - x.PlanStart.Value).Days >= 2) ||
                    (!x.ActualStart.HasValue && (cutoffDate - x.PlanStart.Value).Days >= 2)
                ),
                EndDelayCount = g.Count(x =>
                    (x.ActualEnd.HasValue && (x.ActualEnd.Value - x.PlanEnd.Value).Days >= 2) ||
                    (!x.ActualEnd.HasValue && (cutoffDate - x.PlanEnd.Value).Days >= 2)
                )
            })
            .ToList();

        foreach (var stat in areaStats)
        {
            result.Categories.Add(stat.AreaName);
            result.TotalCounts.Add(stat.TotalCount);
            var totalDelayCount = Math.Max(stat.StartDelayCount, stat.EndDelayCount);
            result.DelayCounts.Add(totalDelayCount);
            var delayRate = stat.TotalCount > 0
                ? Math.Round((decimal)totalDelayCount / stat.TotalCount * 100, 2)
                : 0;
            result.DelayRates.Add(delayRate);
        }
        return result;
    }

    /// <summary>
    /// 各分类延期情况统计
    /// </summary>
    [DisplayName("获取各分类延期情况")]
    [ApiDescriptionSettings(Name = "GetClassDelayCharts"), HttpPost]
    public async Task<PlanDelayChartsOutput> GetClassDelayCharts(PlanDelayAllInput input)
    {
        var result = new PlanDelayChartsOutput();
        
        // 计算截止日期
        var cutoffDate = GetCutoffDate(input.workTime);
        
        // 获取所有数据到内存
        var allList = await GetGlobalFilterDataSource(input)
            .LeftJoin<PlanType>((u, pt) => u.ClassName == pt.Id)
            .Select((u, pt) => new
            {
                ClassName = pt.Name ?? "未知分类",
                PlanStart = u.PlanStart,
                ActualStart = u.ActualStart,
                PlanEnd = u.PlanEnd,
                ActualEnd = u.ActualEnd,
                ClassId = u.ClassName,
                Id = u.Id
            })
            .ToListAsync();

        var classStats = allList
            .GroupBy(x => x.ClassName)
            .Select(g => new
            {
                ClassName = g.Key,
                TotalCount = g.Count(),
                StartDelayCount = g.Count(x =>
                    (x.ActualStart.HasValue && (x.ActualStart.Value - x.PlanStart.Value).Days >= 2) ||
                    (!x.ActualStart.HasValue && (cutoffDate - x.PlanStart.Value).Days >= 2)
                ),
                EndDelayCount = g.Count(x =>
                    (x.ActualEnd.HasValue && (x.ActualEnd.Value - x.PlanEnd.Value).Days >= 2) ||
                    (!x.ActualEnd.HasValue && (cutoffDate - x.PlanEnd.Value).Days >= 2)
                )
            })
            .ToList();

        foreach (var stat in classStats)
        {
            result.Categories.Add(stat.ClassName);
            result.TotalCounts.Add(stat.TotalCount);
            var totalDelayCount = Math.Max(stat.StartDelayCount, stat.EndDelayCount);
            result.DelayCounts.Add(totalDelayCount);
            var delayRate = stat.TotalCount > 0
                ? Math.Round((decimal)totalDelayCount / stat.TotalCount * 100, 2)
                : 0;
            result.DelayRates.Add(delayRate);
        }
        return result;
    }

    #endregion

    #region 获取计划实际延期列表
    [DisplayName("获取计划开工实际延期列表")]
    [ApiDescriptionSettings(Name = "GetPlanStartDelayOutList"), HttpPost]
    public async Task<List<PlanDelayOutputList>> GetPlanStartDelayOutList(PlanDelayCount input)
    {
        var query = GetPlanStartDelayList(input);
        
        // 计算截止日期
        var cutoffDate = GetCutoffDate(input.workTime);
        
        // 连表查询并投影到输出类型（计算所有延期数据）
        var allResults = await query
            .LeftJoin<PlanType>((u, className) => u.ClassName == className.Id)
            .LeftJoin<SysOrg>((u, className, workArea) => u.WorkAreaId == workArea.Id)
            .Select((u, className, workArea) => new PlanDelayOutputList
            {
                Ch = u.Ch,
                Fd = u.Fd,
                ClassName = $"{className.Name}",
                WorkArea = $"{workArea.Name}",
                Team = u.Team,
                PlanStart = u.PlanStart,
                PlanEnd = u.PlanEnd,
                ActualStart = u.ActualStart,
                ActualEnd = u.ActualEnd,
                // 计算开工延期天数：已开工则用实际-计划，未开工则用截止日期-计划
                Delay = u.ActualStart.HasValue 
                    ? SqlFunc.DateDiff(DateType.Day, u.PlanStart.Value, u.ActualStart.Value)
                    : SqlFunc.DateDiff(DateType.Day, u.PlanStart.Value, cutoffDate)
            })
            .ToListAsync();
            
        // 根据延期范围进行分段筛选
        var filteredResults = allResults.Where(item =>
        {
            return input.DelayRange switch
            {
                DelayRangeEnum.Light => item.Delay >= 1 && item.Delay <= 3,   // 1-3天
                DelayRangeEnum.Medium => item.Delay >= 4 && item.Delay <= 7,  // 4-7天  
                DelayRangeEnum.Severe => item.Delay > 7,                      // 7天以上
                DelayRangeEnum.All => item.Delay >= 1,                        // 全部延期：返回所有延期数据（延期天数>=1）
                _ => item.Delay >= 1  // 默认返回所有延期数据（延期天数>=1）
            };
        }).ToList();
            
        return filteredResults;
    }
    [DisplayName("获取计划完工实际延期列表")]
    [ApiDescriptionSettings(Name = "GetPlanEndDelayOutList"), HttpPost]
    public async Task<List<PlanDelayOutputList>> GetPlanEndDelayOutList(PlanDelayCount input)
    {
        var query = GetPlanEndDelayList(input);
        
        // 计算截止日期
        var cutoffDate = GetCutoffDate(input.workTime);
        
        // 连表查询并投影到输出类型（计算所有延期数据）
        var allResults = await query
            .LeftJoin<PlanType>((u, className) => u.ClassName == className.Id)
            .LeftJoin<SysOrg>((u, className, workArea) => u.WorkAreaId == workArea.Id)
            .Select((u, className, workArea) => new PlanDelayOutputList
            {
                Ch = u.Ch,
                Fd = u.Fd,
                ClassName = $"{className.Name}",
                WorkArea = $"{workArea.Name}",
                Team = u.Team,
                PlanStart = u.PlanStart,
                PlanEnd = u.PlanEnd,
                ActualStart = u.ActualStart,
                ActualEnd = u.ActualEnd,
                // 计算完工延期天数：已完工则用实际-计划，未完工则用截止日期-计划
                Delay = u.ActualEnd.HasValue 
                    ? SqlFunc.DateDiff(DateType.Day, u.PlanEnd.Value, u.ActualEnd.Value)
                    : SqlFunc.DateDiff(DateType.Day, u.PlanEnd.Value, cutoffDate)
            })
            .ToListAsync();
            
        // 根据延期范围进行分段筛选
        var filteredResults = allResults.Where(item =>
        {
            return input.DelayRange switch
            {
                DelayRangeEnum.Light => item.Delay >= 1 && item.Delay <= 3,   // 1-3天
                DelayRangeEnum.Medium => item.Delay >= 4 && item.Delay <= 7,  // 4-7天  
                DelayRangeEnum.Severe => item.Delay > 7,                      // 7天以上
                DelayRangeEnum.All => item.Delay >= 1,                        // 全部延期：返回所有延期数据（延期天数>=1）
                _ => item.Delay >= 1  // 默认返回所有延期数据（延期天数>=1）
            };
        }).ToList();
            
        return filteredResults;
    }
    #endregion

    #region 延期看板专用接口
    [DisplayName("获取延期看板计划开工延期列表")]
    [ApiDescriptionSettings(Name = "GetPlanStartDelayForBoard"), HttpPost]
    public async Task<List<PlanDelayOutputList>> GetPlanStartDelayForBoard(PlanDelayBoardInput input)
    {
        // 获取月份范围
        var (startDate, endDate) = DateTimeUtil.CalculateWorkTimeRange(input.workTime);
        
        // 构建查询：只筛选PlanStart在月份范围内的数据
        var query = _planListRep.AsQueryable();
        
        // 月份筛选：只筛选PlanStart在月份范围内的数据
        query = query.Where(u => u.PlanStart >= startDate && u.PlanStart <= endDate);
        
        // 应用筛选条件
        query = query.WhereIF(input.ClassName != null, u => u.ClassName == input.ClassName);
        query = query.WhereIF(input.WorkAreaId != null, u => u.WorkAreaId == input.WorkAreaId);
        
        // 只显示未开工的数据
        query = query.Where(u => !u.ActualStart.HasValue);
        
        // 计算截止日期
        var cutoffDate = GetCutoffDate(input.workTime);
        
        // 连表查询并投影到输出类型
        var allResults = await query
            .LeftJoin<PlanType>((u, className) => u.ClassName == className.Id)
            .LeftJoin<SysOrg>((u, className, workArea) => u.WorkAreaId == workArea.Id)
            .Select((u, className, workArea) => new PlanDelayOutputList
            {
                Ch = u.Ch,
                Fd = u.Fd,
                ClassName = $"{className.Name}",
                WorkArea = $"{workArea.Name}",
                Team = u.Team,
                PlanStart = u.PlanStart,
                PlanEnd = u.PlanEnd,
                ActualStart = u.ActualStart,
                ActualEnd = u.ActualEnd,
                // 计算开工延期天数：未开工用截止日期-计划
                Delay = SqlFunc.DateDiff(DateType.Day, u.PlanStart.Value, cutoffDate)
            })
            .ToListAsync();
            
        // 只返回延期>=指定天数的数据
        var filteredResults = allResults.Where(item => item.Delay >= input.MinDelayDays).ToList();
            
        return filteredResults;
    }

    [DisplayName("获取延期看板计划完工延期列表")]
    [ApiDescriptionSettings(Name = "GetPlanEndDelayForBoard"), HttpPost]
    public async Task<List<PlanDelayOutputList>> GetPlanEndDelayForBoard(PlanDelayBoardInput input)
    {
        // 获取月份范围
        var (startDate, endDate) = DateTimeUtil.CalculateWorkTimeRange(input.workTime);
        
        // 构建查询：只筛选PlanEnd在月份范围内的数据
        var query = _planListRep.AsQueryable();
        
        // 月份筛选：只筛选PlanEnd在月份范围内的数据
        query = query.Where(u => u.PlanEnd >= startDate && u.PlanEnd <= endDate);
        
        // 应用筛选条件
        query = query.WhereIF(input.ClassName != null, u => u.ClassName == input.ClassName);
        query = query.WhereIF(input.WorkAreaId != null, u => u.WorkAreaId == input.WorkAreaId);
        
        // 只显示未完工的数据
        query = query.Where(u => !u.ActualEnd.HasValue);
        
        // 计算截止日期
        var cutoffDate = GetCutoffDate(input.workTime);
        
        // 连表查询并投影到输出类型
        var allResults = await query
            .LeftJoin<PlanType>((u, className) => u.ClassName == className.Id)
            .LeftJoin<SysOrg>((u, className, workArea) => u.WorkAreaId == workArea.Id)
            .Select((u, className, workArea) => new PlanDelayOutputList
            {
                Ch = u.Ch,
                Fd = u.Fd,
                ClassName = $"{className.Name}",
                WorkArea = $"{workArea.Name}",
                Team = u.Team,
                PlanStart = u.PlanStart,
                PlanEnd = u.PlanEnd,
                ActualStart = u.ActualStart,
                ActualEnd = u.ActualEnd,
                // 计算完工延期天数：未完工用截止日期-计划
                Delay = SqlFunc.DateDiff(DateType.Day, u.PlanEnd.Value, cutoffDate)
            })
            .ToListAsync();
            
        // 只返回延期>=指定天数的数据
        var filteredResults = allResults.Where(item => item.Delay >= input.MinDelayDays).ToList();
            
        return filteredResults;
    }

    /// <summary>
    /// 获取延期看板可用的作业区下拉（后端生成，避免前端依赖命名约定）
    /// 规则：
    ///  - 业务月范围内，有计划记录关联的作业区
    ///  - 去重后按名称排序
    /// </summary>
    [DisplayName("获取延期看板可用作业区")]
    [ApiDescriptionSettings(Name = "GetWorkAreasForBoard"), HttpPost]
    public async Task<List<OptionDto>> GetWorkAreasForBoard(PlanDelayAllInput input)
    {
        var (startDate, endDate) = DateTimeUtil.CalculateWorkTimeRange(input.workTime);

        // 从 PlanList 里取出范围内涉及到的 WorkAreaId，然后关联 SysOrg 获取名称
        var items = await _sqlSugarClient.Queryable<PlanList>()
            .LeftJoin<SysOrg>((p, o) => p.WorkAreaId == o.Id)
            .Where(p =>
                (p.PlanStart >= startDate && p.PlanStart <= endDate) ||
                (p.PlanEnd >= startDate && p.PlanEnd <= endDate))
            .WhereIF(input.ClassName != null, (p, o) => p.ClassName == input.ClassName)
            .GroupBy((p, o) => new { o.Id, o.Name })
            .OrderBy((p, o) => o.Name)
            .Select((p, o) => new OptionDto { Value = o.Id, Label = o.Name })
            .ToListAsync();

        // 过滤空名
        items = items.Where(i => !string.IsNullOrWhiteSpace(i.Label)).ToList();
        return items;
    }
    #endregion

    #region 延期通知推送功能

    /// <summary>
    /// 发送延期通知给相关作业区人员
    /// </summary>
    [DisplayName("发送延期通知")]
    [ApiDescriptionSettings(Name = "SendDelayNotifications"), HttpPost]
    public async Task<DelayNotificationOutput> SendDelayNotifications(DelayNotificationInput input)
    {
        var result = new DelayNotificationOutput();
        
        try
        {
            // 获取延期数据
            var delayItems = await GetDelayItemsForNotification(input);
            
            if (delayItems == null || delayItems.Count == 0)
            {
                result.Success = true;
                result.ErrorMessage = "没有找到符合条件的延期数据";
                return result;
            }

            // 按作业区域分组
            var groupedDelays = delayItems
                .Where(item => !string.IsNullOrEmpty(item.WorkArea))
                .GroupBy(item => new { WorkAreaName = item.WorkArea })
                .ToList();

            result.TotalDelayCount = delayItems.Count;
            result.AffectedWorkAreaCount = groupedDelays.Count;

            // 获取所有需要通知的用户及其有权限的区域
            var userAreaPermissions = await GetUserAreaPermissions(input);
            
            // 为每个用户发送他们有权限的区域的延期通知
            var userNotifications = new Dictionary<long, List<(string WorkAreaName, List<PlanDelayOutputList> Delays)>>();
            
            foreach (var areaGroup in groupedDelays)
            {
                var areaDelays = areaGroup.ToList();
                var workAreaName = areaGroup.Key.WorkAreaName;

                // 找出有权限查看该区域的用户
                var authorizedUsers = userAreaPermissions
                    .Where(up => up.Value.Contains(workAreaName))
                    .Select(up => up.Key)
                    .ToList();

                foreach (var userId in authorizedUsers)
                {
                    if (!userNotifications.ContainsKey(userId))
                    {
                        userNotifications[userId] = new List<(string, List<PlanDelayOutputList>)>();
                    }
                    userNotifications[userId].Add((workAreaName, areaDelays));
                }
            }

            // 为每个用户发送个性化的延期通知
            foreach (var userNotification in userNotifications)
            {
                var userId = userNotification.Key;
                var userAreas = userNotification.Value;

                // 构建该用户的延期通知消息
                var userMessage = BuildUserDelayNotificationMessage(userAreas, input);
                
                // 发送持久化通知
                await _sysNoticeService.PublicToUsers(
                    title: userMessage.Title,
                    content: userMessage.Message,
                    userIds: new List<long> { userId },
                    noticeType: NoticeTypeEnum.NOTICE
                );

                // 统计详情
                foreach (var (workAreaName, areaDelays) in userAreas)
                {
                    var detail = new DelayNotificationDetail
                    {
                        WorkAreaName = workAreaName,
                        DelayItemCount = areaDelays.Count,
                        SevereDelayCount = areaDelays.Count(d => d.Delay > 7),
                        MediumDelayCount = areaDelays.Count(d => d.Delay >= 4 && d.Delay <= 7),
                        LightDelayCount = areaDelays.Count(d => d.Delay >= 1 && d.Delay <= 3),
                        NotifiedUserIds = new List<long> { userId },
                        NotifiedUserNames = new List<string> { await GetUserNameById(userId) }
                    };
                    
                    result.Details.Add(detail);
                }
                
                result.NotifiedUserCount++;
            }

            result.Success = true;
        }
        catch (Exception ex)
        {
            result.Success = false;
            result.ErrorMessage = $"推送延期通知失败：{ex.Message}";
        }

        return result;
    }

    /// <summary>
    /// 预览延期通知接收人 📢
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("预览延期通知接收人")]
    [ApiDescriptionSettings(Name = "PreviewNotificationRecipients"), HttpPost]
    public async Task<NotificationRecipientsPreview> PreviewNotificationRecipients(DelayNotificationInput input)
    {
        var preview = new NotificationRecipientsPreview();

        try
        {
            // 获取所有相关的延期数据
            var allDelayItems = await GetDelayItemsForNotification(input);
            
            if (allDelayItems.Count == 0)
            {
                preview.Message = "未找到符合条件的延期数据，无需发送通知。";
                return preview;
            }

            // 获取用户及其有权限的区域列表
            var userAreaPermissions = await GetUserAreaPermissions(input);
            
            // 按作业区域分组延期数据
            var groupedDelays = allDelayItems.GroupBy(item => new { WorkAreaName = item.WorkArea });

            // 为每个用户计算他们有权限的区域的延期信息
            var userAreaDelays = new Dictionary<long, List<(string WorkAreaName, List<PlanDelayOutputList> Delays)>>();

            foreach (var areaGroup in groupedDelays)
            {
                var workAreaName = areaGroup.Key.WorkAreaName;
                var areaDelays = areaGroup.ToList();

                // 找出有权限查看该区域的用户
                var authorizedUsers = userAreaPermissions
                    .Where(up => up.Value.Contains(workAreaName))
                    .Select(up => up.Key)
                    .ToList();

                foreach (var userId in authorizedUsers)
                {
                    if (!userAreaDelays.ContainsKey(userId))
                    {
                        userAreaDelays[userId] = new List<(string, List<PlanDelayOutputList>)>();
                    }
                    userAreaDelays[userId].Add((workAreaName, areaDelays));
                }
            }

            // 构建预览信息
            var userRecipients = new Dictionary<long, NotificationRecipient>();
            
            foreach (var userDelay in userAreaDelays)
            {
                var userId = userDelay.Key;
                var userAreas = userDelay.Value;

                // 获取用户信息
                var user = await _sysUserRep.GetByIdAsync(userId);
                if (user != null)
                {
                    var recipient = new NotificationRecipient
                    {
                        UserId = user.Id,
                        UserName = user.RealName ?? user.Account,
                        Account = user.Account,
                        Phone = user.Phone,
                        Email = user.Email
                    };

                    userRecipients[userId] = recipient;

                    // 为每个用户创建区域预览
                    foreach (var (workAreaName, areaDelays) in userAreas)
                    {
                        var existingAreaPreview = preview.WorkAreas.FirstOrDefault(w => w.WorkAreaName == workAreaName);
                        if (existingAreaPreview == null)
                        {
                            existingAreaPreview = new WorkAreaRecipientsPreview
                            {
                                WorkAreaName = workAreaName,
                                DelayItemCount = areaDelays.Count,
                                Recipients = new List<NotificationRecipient>()
                            };
                            preview.WorkAreas.Add(existingAreaPreview);
                        }

                        // 检查是否已经添加过该用户
                        if (!existingAreaPreview.Recipients.Any(r => r.UserId == userId))
                        {
                            existingAreaPreview.Recipients.Add(recipient);
                        }
                    }
                }
            }

            preview.TotalDelayItemCount = allDelayItems.Count;
            preview.AffectedWorkAreaCount = preview.WorkAreas.Count;
            preview.TotalRecipientCount = userRecipients.Count;
            preview.Success = true;
            
            if (preview.TotalRecipientCount == 0)
            {
                preview.Message = "根据当前配置，没有找到有效的通知接收人。";
            }
            else
            {
                preview.Message = $"将向 {preview.TotalRecipientCount} 位用户发送 {preview.TotalDelayItemCount} 项延期通知，涉及 {preview.AffectedWorkAreaCount} 个作业区域。每位用户将只收到其有权限的区域的延期信息。";
            }
        }
        catch (Exception ex)
        {
            preview.Success = false;
            preview.Message = $"预览通知接收人失败：{ex.Message}";
        }

        return preview;
    }

    /// <summary>
    /// 获取用于通知的延期数据
    /// </summary>
    private async Task<List<PlanDelayOutputList>> GetDelayItemsForNotification(DelayNotificationInput input)
    {
        var allDelayItems = new List<PlanDelayOutputList>();

        // 根据通知类型获取对应的延期数据
        if (input.NotificationType == DelayNotificationTypeEnum.Start || input.NotificationType == DelayNotificationTypeEnum.All)
        {
            var startDelayInput = new PlanDelayCount
            {
                workTime = input.workTime,
                ClassName = input.ClassName,
                WorkAreaId = input.WorkAreaId,
                PlanStart = false // 获取未开工的延期数据
            };

            // 根据延期等级筛选
            foreach (var delayLevel in input.DelayLevels)
            {
                if (delayLevel == DelayRangeEnum.All)
                {
                    // 获取所有延期等级的数据
                    var allLevels = new[] { DelayRangeEnum.Light, DelayRangeEnum.Medium, DelayRangeEnum.Severe };
                    foreach (var level in allLevels)
                    {
                        startDelayInput.DelayRange = level;
                        var items = await GetPlanStartDelayOutList(startDelayInput);
                        allDelayItems.AddRange(items);
                    }
                }
                else
                {
                    startDelayInput.DelayRange = delayLevel;
                    var items = await GetPlanStartDelayOutList(startDelayInput);
                    allDelayItems.AddRange(items);
                }
            }
        }

        if (input.NotificationType == DelayNotificationTypeEnum.End || input.NotificationType == DelayNotificationTypeEnum.All)
        {
            var endDelayInput = new PlanDelayCount
            {
                workTime = input.workTime,
                ClassName = input.ClassName,
                WorkAreaId = input.WorkAreaId,
                PlanStart = false // 获取未完工的延期数据
            };

            // 根据延期等级筛选
            foreach (var delayLevel in input.DelayLevels)
            {
                if (delayLevel == DelayRangeEnum.All)
                {
                    // 获取所有延期等级的数据
                    var allLevels = new[] { DelayRangeEnum.Light, DelayRangeEnum.Medium, DelayRangeEnum.Severe };
                    foreach (var level in allLevels)
                    {
                        endDelayInput.DelayRange = level;
                        var items = await GetPlanEndDelayOutList(endDelayInput);
                        allDelayItems.AddRange(items);
                    }
                }
                else
                {
                    endDelayInput.DelayRange = delayLevel;
                    var items = await GetPlanEndDelayOutList(endDelayInput);
                    allDelayItems.AddRange(items);
                }
            }
        }

        // 去重（避免同时获取开工和完工延期时的重复数据）
        return allDelayItems.DistinctBy(item => item.Ch + item.Fd).ToList();
    }

    /// <summary>
    /// 根据作业区域名称获取相关用户 - 按区域和部门层级过滤有权限的用户
    /// </summary>
    private async Task<List<SysUser>> GetUsersByWorkAreaName(string workAreaName, List<long>? excludeUserIds = null)
    {
        var users = new List<SysUser>();

        try
        {
            // 首先根据区域名称查找组织ID
            var targetOrg = await _sqlSugarClient.Queryable<SysOrg>()
                .FirstAsync(o => o.Name == workAreaName);

            if (targetOrg == null)
            {
                return users;
            }

            // 获取该区域及其子部门的所有组织ID
            var targetOrgIds = await GetOrgAndChildrenIds(targetOrg.Id);

            // 获取有权限查看该区域数据的用户
            var authorizedUsers = await GetAuthorizedUsersForOrg(targetOrgIds);

            users.AddRange(authorizedUsers);

            // 排除指定的用户
            if (excludeUserIds != null && excludeUserIds.Count > 0)
            {
                users = users.Where(u => !excludeUserIds.Contains(u.Id)).ToList();
            }
        }
        catch (Exception ex)
        {
            // 使用结构化日志记录错误,不影响主流程
            _logger.LogError(ex,
                "获取作业区域 {WorkAreaName} 的用户时出错",
                workAreaName);
        }

        return users;
    }

    /// <summary>
    /// 获取组织及其所有子级组织的ID列表
    /// </summary>
    private async Task<List<long>> GetOrgAndChildrenIds(long orgId)
    {
        var orgIds = new List<long> { orgId };

        // 递归获取所有子部门
        var childOrgs = await _sqlSugarClient.Queryable<SysOrg>()
            .Where(o => o.Pid == orgId)
            .ToListAsync();

        foreach (var childOrg in childOrgs)
        {
            var childOrgIds = await GetOrgAndChildrenIds(childOrg.Id);
            orgIds.AddRange(childOrgIds);
        }

        return orgIds.Distinct().ToList();
    }

    /// <summary>
    /// 获取有权限查看指定组织数据的用户
    /// 原则：给对应区域的本部门及以下数据的角色
    /// </summary>
    private async Task<List<SysUser>> GetAuthorizedUsersForOrg(List<long> targetOrgIds)
    {
        var authorizedUsers = new List<SysUser>();

        // 1. 获取所有启用的用户及其角色信息
        var usersWithRoles = await _sqlSugarClient.Queryable<SysUser>()
            .LeftJoin<SysUserRole>((u, ur) => u.Id == ur.UserId)
            .LeftJoin<SysRole>((u, ur, r) => ur.RoleId == r.Id)
            .Where(u => u.Status == StatusEnum.Enable)
            .Select((u, ur, r) => new
            {
                User = u,
                Role = r
            })
            .ToListAsync();

        // 按用户分组处理
        var userGroups = usersWithRoles.GroupBy(x => x.User.Id);

        foreach (var userGroup in userGroups)
        {
            var user = userGroup.First().User;
            var userRoles = userGroup.Where(x => x.Role != null).Select(x => x.Role).ToList();

            // 检查用户是否有权限访问目标组织的数据
            if (await HasOrgDataPermission(user, userRoles, targetOrgIds))
            {
                authorizedUsers.Add(user);
            }
        }

        return authorizedUsers.DistinctBy(u => u.Id).ToList();
    }

    /// <summary>
    /// 检查用户是否有权限访问指定组织的数据
    /// </summary>
    private async Task<bool> HasOrgDataPermission(SysUser user, List<SysRole> userRoles, List<long> targetOrgIds)
    {
        // 超级管理员和系统管理员有全部权限
        if (user.AccountType == AccountTypeEnum.SuperAdmin || user.AccountType == AccountTypeEnum.SysAdmin)
        {
            return true;
        }

        // 如果用户直属于目标组织或其子组织
        if (targetOrgIds.Contains(user.OrgId))
        {
            return true;
        }

        // 检查用户的扩展组织
        var userExtOrgs = await _sqlSugarClient.Queryable<SysUserExtOrg>()
            .Where(ext => ext.UserId == user.Id)
            .ToListAsync();

        if (userExtOrgs.Any(ext => targetOrgIds.Contains(ext.OrgId)))
        {
            return true;
        }

        // 检查用户角色的数据权限
        foreach (var role in userRoles)
        {
            if (await CheckRoleDataScope(role, user, targetOrgIds))
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// 检查角色数据权限
    /// </summary>
    private async Task<bool> CheckRoleDataScope(SysRole role, SysUser user, List<long> targetOrgIds)
    {
        switch (role.DataScope)
        {
            case DataScopeEnum.All:
                // 全部数据权限
                return true;

            case DataScopeEnum.DeptChild:
                // 本部门及以下数据权限
                if (user.OrgId > 0)
                {
                    var userOrgAndChildren = await GetOrgAndChildrenIds(user.OrgId);
                    return targetOrgIds.Any(id => userOrgAndChildren.Contains(id));
                }
                break;

            case DataScopeEnum.Dept:
                // 本部门数据权限
                if (user.OrgId > 0)
                {
                    return targetOrgIds.Contains(user.OrgId);
                }
                break;

            case DataScopeEnum.Self:
                // 仅本人数据权限 - 对于组织通知不适用
                return false;

            case DataScopeEnum.Define:
                // 自定义数据权限 - 检查角色关联的组织
                var roleOrgs = await _sqlSugarClient.Queryable<SysRoleOrg>()
                    .Where(ro => ro.RoleId == role.Id)
                    .ToListAsync();

                var roleOrgIds = roleOrgs.Select(ro => ro.OrgId).ToList();
                return targetOrgIds.Any(id => roleOrgIds.Contains(id));
        }

        return false;
    }

    /// <summary>
    /// 根据配置模式获取通知接收人
    /// </summary>
    private async Task<List<SysUser>> GetNotificationRecipients(string workAreaName, DelayNotificationInput input)
    {
        var users = new List<SysUser>();

        switch (input.RecipientMode)
        {
            case NotificationRecipientMode.AutoByPermission:
                // 原有逻辑：根据区域权限自动确定接收人
                users = await GetUsersByWorkAreaName(workAreaName, input.ExcludeUserIds);
                break;

            case NotificationRecipientMode.SpecificUsers:
                // 指定用户模式
                if (input.SpecificUserIds != null && input.SpecificUserIds.Count > 0)
                {
                    users = await GetUsersByIds(input.SpecificUserIds, input.ExcludeUserIds);
                }
                break;

            case NotificationRecipientMode.SpecificRoles:
                // 指定角色模式
                if (input.SpecificRoleIds != null && input.SpecificRoleIds.Count > 0)
                {
                    users = await GetUsersByRoleIds(input.SpecificRoleIds, input.IncludeSubordinates, input.ExcludeUserIds);
                }
                break;

            case NotificationRecipientMode.Mixed:
                // 混合模式：同时包含指定用户和角色用户
                var specificUsers = input.SpecificUserIds != null && input.SpecificUserIds.Count > 0
                    ? await GetUsersByIds(input.SpecificUserIds, input.ExcludeUserIds)
                    : new List<SysUser>();

                var roleUsers = input.SpecificRoleIds != null && input.SpecificRoleIds.Count > 0
                    ? await GetUsersByRoleIds(input.SpecificRoleIds, input.IncludeSubordinates, input.ExcludeUserIds)
                    : new List<SysUser>();

                users.AddRange(specificUsers);
                users.AddRange(roleUsers);
                users = users.DistinctBy(u => u.Id).ToList();
                break;
        }

        return users;
    }

    /// <summary>
    /// 根据用户ID列表获取用户
    /// </summary>
    private async Task<List<SysUser>> GetUsersByIds(List<long> userIds, List<long>? excludeUserIds = null)
    {
        var query = _sysUserRep.AsQueryable()
            .Where(u => userIds.Contains(u.Id))
            .Where(u => u.Status == StatusEnum.Enable);

        if (excludeUserIds != null && excludeUserIds.Count > 0)
        {
            query = query.Where(u => !excludeUserIds.Contains(u.Id));
        }

        return await query.ToListAsync();
    }

    /// <summary>
    /// 根据角色ID列表获取用户
    /// </summary>
    private async Task<List<SysUser>> GetUsersByRoleIds(List<long> roleIds, bool includeSubordinates = false, List<long>? excludeUserIds = null)
    {
        var users = new List<SysUser>();

        // 获取角色用户
        var roleUsers = await _sqlSugarClient.Queryable<SysUser>()
            .InnerJoin<SysUserRole>((u, ur) => u.Id == ur.UserId)
            .Where((u, ur) => roleIds.Contains(ur.RoleId))
            .Where(u => u.Status == StatusEnum.Enable)
            .Select((u, ur) => u)
            .ToListAsync();

        users.AddRange(roleUsers);

        // 如果包含下级用户，获取这些用户管理的下级用户
        if (includeSubordinates)
        {
            var managerUserIds = roleUsers.Select(u => u.Id).ToList();
            var subordinateUsers = await _sqlSugarClient.Queryable<SysUser>()
                .Where(u => managerUserIds.Contains(u.ManagerUserId ?? 0))
                .Where(u => u.Status == StatusEnum.Enable)
                .ToListAsync();

            users.AddRange(subordinateUsers);
        }

        // 排除指定用户
        if (excludeUserIds != null && excludeUserIds.Count > 0)
        {
            users = users.Where(u => !excludeUserIds.Contains(u.Id)).ToList();
        }

        return users.DistinctBy(u => u.Id).ToList();
    }

    /// <summary>
    /// 获取用户及其有权限的区域列表
    /// </summary>
    private async Task<Dictionary<long, List<string>>> GetUserAreaPermissions(DelayNotificationInput input)
    {
        var userAreaPermissions = new Dictionary<long, List<string>>();

        // 获取所有相关的延期数据
        var allDelayItems = await GetDelayItemsForNotification(input);
        var allWorkAreas = allDelayItems.Select(item => item.WorkArea).Distinct().ToList();
        
        switch (input.RecipientMode)
        {
            case NotificationRecipientMode.AutoByPermission:
                // 自动按权限配置：获取所有有权限的用户
                foreach (var workArea in allWorkAreas)
                {
                    var areaUsers = await GetUsersByWorkAreaName(workArea, input.ExcludeUserIds);
                    foreach (var user in areaUsers)
                    {
                        if (!userAreaPermissions.ContainsKey(user.Id))
                        {
                            userAreaPermissions[user.Id] = new List<string>();
                        }
                        if (!userAreaPermissions[user.Id].Contains(workArea))
                        {
                            userAreaPermissions[user.Id].Add(workArea);
                        }
                    }
                }
                break;

            case NotificationRecipientMode.SpecificUsers:
                // 指定用户模式：只给用户分配他们有权限且有延期数据的区域
                if (input.SpecificUserIds != null && input.SpecificUserIds.Count > 0)
                {
                    var users = await GetUsersByIds(input.SpecificUserIds, input.ExcludeUserIds);
                    foreach (var user in users)
                    {
                        var userAuthorizedAreas = await GetUserAuthorizedWorkAreas(user);
                        // 只分配用户有权限且有延期数据的区域
                        var authorizedAreasWithDelays = userAuthorizedAreas.Intersect(allWorkAreas).ToList();
                        if (authorizedAreasWithDelays.Any())
                        {
                            userAreaPermissions[user.Id] = authorizedAreasWithDelays;
                        }
                    }
                }
                break;

            case NotificationRecipientMode.SpecificRoles:
                // 指定角色模式：只给角色用户分配他们有权限且有延期数据的区域
                if (input.SpecificRoleIds != null && input.SpecificRoleIds.Count > 0)
                {
                    var users = await GetUsersByRoleIds(input.SpecificRoleIds, input.IncludeSubordinates, input.ExcludeUserIds);
                    foreach (var user in users)
                    {
                        var userAuthorizedAreas = await GetUserAuthorizedWorkAreas(user);
                        // 只分配用户有权限且有延期数据的区域
                        var authorizedAreasWithDelays = userAuthorizedAreas.Intersect(allWorkAreas).ToList();
                        if (authorizedAreasWithDelays.Any())
                        {
                            userAreaPermissions[user.Id] = authorizedAreasWithDelays;
                        }
                    }
                }
                break;

            case NotificationRecipientMode.Mixed:
                // 混合模式：同时包含指定用户和角色用户
                var specificUsers = input.SpecificUserIds != null && input.SpecificUserIds.Count > 0
                    ? await GetUsersByIds(input.SpecificUserIds, input.ExcludeUserIds)
                    : new List<SysUser>();

                var roleUsers = input.SpecificRoleIds != null && input.SpecificRoleIds.Count > 0
                    ? await GetUsersByRoleIds(input.SpecificRoleIds, input.IncludeSubordinates, input.ExcludeUserIds)
                    : new List<SysUser>();

                var allUsers = specificUsers.Union(roleUsers).DistinctBy(u => u.Id).ToList();
                foreach (var user in allUsers)
                {
                    var userAuthorizedAreas = await GetUserAuthorizedWorkAreas(user);
                    // 只分配用户有权限且有延期数据的区域
                    var authorizedAreasWithDelays = userAuthorizedAreas.Intersect(allWorkAreas).ToList();
                    if (authorizedAreasWithDelays.Any())
                    {
                        userAreaPermissions[user.Id] = authorizedAreasWithDelays;
                    }
                }
                break;
        }

        return userAreaPermissions;
    }

    /// <summary>
    /// 计算截止日期：当前月份使用今天，历史月份和未来月份使用月份结束日期
    /// </summary>
    /// <param name="workTime">筛选月份，格式如 '2025-07'</param>
    /// <returns>截止日期</returns>
    private DateTime GetCutoffDate(string workTime)
    {
        var (startDate, endDate) = DateTimeUtil.CalculateWorkTimeRange(workTime);                                    
        var today = DateTime.Now.Date;
        
        // 判断是否为当前业务月份：今天在业务月份范围内 且 不是未来月份
        bool isCurrentWorkMonth = today >= startDate && today <= endDate.Date;
        
        // 额外检查：如果是未来月份，即使今天在范围内也使用月份结束日期
        // 通过比较业务月份的开始日期是否大于今天来判断是否为未来月份
        bool isFutureMonth = startDate > today;
        
        // 当前月份且不是未来月份时使用今天的结束时间，否则使用月份结束日期
        return isCurrentWorkMonth && !isFutureMonth ? today.AddDays(1).AddMilliseconds(-1) : endDate.Date;
    }

    /// <summary>
    /// 获取用户有权限的作业区域列表
    /// </summary>
    private async Task<List<string>> GetUserAuthorizedWorkAreas(SysUser user)
    {
        var authorizedWorkAreas = new List<string>();

        // 超级管理员和系统管理员有全部权限
        if (user.AccountType == AccountTypeEnum.SuperAdmin || user.AccountType == AccountTypeEnum.SysAdmin)
        {
            // 获取所有作业区域（根据名称包含"作业区"来识别）
            var allOrgs = await _sqlSugarClient.Queryable<SysOrg>()
                .Where(o => o.Name.Contains("作业区")) // 根据名称识别作业区域
                .Select(o => o.Name)
                .ToListAsync();
            return allOrgs;
        }

        // 获取用户所属组织及其子组织
        var userOrgIds = new List<long> { user.OrgId };
        
        // 获取用户的扩展组织
        var userExtOrgs = await _sqlSugarClient.Queryable<SysUserExtOrg>()
            .Where(ext => ext.UserId == user.Id)
            .ToListAsync();
        userOrgIds.AddRange(userExtOrgs.Select(ext => ext.OrgId));

        // 获取用户角色的数据权限
        var userRoles = await _sqlSugarClient.Queryable<SysUserRole>()
            .LeftJoin<SysRole>((ur, r) => ur.RoleId == r.Id)
            .Where((ur, r) => ur.UserId == user.Id)
            .Select((ur, r) => r)
            .ToListAsync();

        foreach (var role in userRoles)
        {
            var roleOrgIds = await GetRoleAuthorizedOrgIds(role, user);
            userOrgIds.AddRange(roleOrgIds);
        }

        // 去重
        userOrgIds = userOrgIds.Distinct().ToList();

        // 获取这些组织对应的作业区域名称（只返回名称包含"作业区"的组织）
        var workAreaNames = await _sqlSugarClient.Queryable<SysOrg>()
            .Where(o => userOrgIds.Contains(o.Id) && o.Name.Contains("作业区"))
            .Select(o => o.Name)
            .ToListAsync();

        return workAreaNames;
    }

    /// <summary>
    /// 获取角色有权限的组织ID列表
    /// </summary>
    private async Task<List<long>> GetRoleAuthorizedOrgIds(SysRole role, SysUser user)
    {
        var orgIds = new List<long>();

        switch (role.DataScope)
        {
            case DataScopeEnum.All:
                // 全部数据权限
                var allOrgs = await _sqlSugarClient.Queryable<SysOrg>()
                    .Select(o => o.Id)
                    .ToListAsync();
                return allOrgs;

            case DataScopeEnum.DeptChild:
                // 本部门及以下数据权限
                if (user.OrgId > 0)
                {
                    return await GetOrgAndChildrenIds(user.OrgId);
                }
                break;

            case DataScopeEnum.Dept:
                // 本部门数据权限
                if (user.OrgId > 0)
                {
                    return new List<long> { user.OrgId };
                }
                break;

            case DataScopeEnum.Define:
                // 自定义数据权限
                var roleOrgs = await _sqlSugarClient.Queryable<SysRoleOrg>()
                    .Where(ro => ro.RoleId == role.Id)
                    .Select(ro => ro.OrgId)
                    .ToListAsync();
                return roleOrgs;
        }

        return orgIds;
    }

    /// <summary>
    /// 根据用户ID获取用户名
    /// </summary>
    private async Task<string> GetUserNameById(long userId)
    {
        try
        {
            var user = await _sysUserRep.GetByIdAsync(userId);
            return user?.RealName ?? user?.Account ?? $"用户{userId}";
        }
        catch
        {
            return $"用户{userId}";
        }
    }

    /// <summary>
    /// 构建用户个性化的延期通知消息
    /// </summary>
    private (string Title, string Message) BuildUserDelayNotificationMessage(List<(string WorkAreaName, List<PlanDelayOutputList> Delays)> userAreas, DelayNotificationInput input)
    {
        var totalDelayCount = userAreas.Sum(area => area.Delays.Count);
        var totalSevereCount = userAreas.Sum(area => area.Delays.Count(d => d.Delay > 7));
        var totalMediumCount = userAreas.Sum(area => area.Delays.Count(d => d.Delay >= 4 && d.Delay <= 7));
        var totalLightCount = userAreas.Sum(area => area.Delays.Count(d => d.Delay >= 1 && d.Delay <= 3));

        var notificationTypeText = input.NotificationType switch
        {
            DelayNotificationTypeEnum.Start => "开工延期",
            DelayNotificationTypeEnum.End => "完工延期",
            DelayNotificationTypeEnum.All => "延期",
            _ => "延期"
        };

        var title = $"您负责的区域{notificationTypeText}警报";
        
        var message = string.IsNullOrEmpty(input.CustomMessage)
            ? $"您负责的区域发现 {totalDelayCount} 项{notificationTypeText}：" +
              $"严重延期(>7天) {totalSevereCount} 项，" +
              $"中等延期(4-7天) {totalMediumCount} 项，" +
              $"轻微延期(1-3天) {totalLightCount} 项。" +
              $"涉及区域：{string.Join("、", userAreas.Select(a => a.WorkAreaName))}。" +
              $"请及时关注和处理！"
            : input.CustomMessage.Replace("{WorkAreaName}", string.Join("、", userAreas.Select(a => a.WorkAreaName)))
                                 .Replace("{TotalCount}", totalDelayCount.ToString())
                                 .Replace("{SevereCount}", totalSevereCount.ToString())
                                 .Replace("{MediumCount}", totalMediumCount.ToString())
                                 .Replace("{LightCount}", totalLightCount.ToString());

        return (title, message);
    }

    /// <summary>
    /// 构建延期通知消息
    /// </summary>
    private (string Title, string Message) BuildDelayNotificationMessage(List<PlanDelayOutputList> delays, string workAreaName, DelayNotificationInput input)
    {
        var severeCounts = delays.Count(d => d.Delay > 7);
        var mediumCounts = delays.Count(d => d.Delay >= 4 && d.Delay <= 7);
        var lightCounts = delays.Count(d => d.Delay >= 1 && d.Delay <= 3);

        var notificationTypeText = input.NotificationType switch
        {
            DelayNotificationTypeEnum.Start => "开工延期",
            DelayNotificationTypeEnum.End => "完工延期",
            DelayNotificationTypeEnum.All => "延期",
            _ => "延期"
        };

        var title = $"{workAreaName} {notificationTypeText}警报";
        
        var message = string.IsNullOrEmpty(input.CustomMessage)
            ? $"【{workAreaName}】发现 {delays.Count} 项{notificationTypeText}：" +
              $"严重延期(>7天) {severeCounts} 项，" +
              $"中等延期(4-7天) {mediumCounts} 项，" +
              $"轻微延期(1-3天) {lightCounts} 项。" +
              $"请及时关注和处理！"
            : input.CustomMessage.Replace("{WorkAreaName}", workAreaName)
                                 .Replace("{TotalCount}", delays.Count.ToString())
                                 .Replace("{SevereCount}", severeCounts.ToString())
                                 .Replace("{MediumCount}", mediumCounts.ToString())
                                 .Replace("{LightCount}", lightCounts.ToString());

        return (title, message);
    }

    #endregion

    #region 导出Excel功能

    /// <summary>
    /// 导出计划开工延期列表Excel
    /// </summary>
    [DisplayName("导出计划开工延期列表Excel")]
    [ApiDescriptionSettings(Name = "ExportPlanStartDelayExcel"), HttpPost]
    public async Task<IActionResult> ExportPlanStartDelayExcel(PlanDelayCount input)
    {
        var delayList = await GetPlanStartDelayOutList(input);
        return await CommonUtil.ExportExcelData(delayList, "计划开工延期明细");
    }

    /// <summary>
    /// 导出计划完工延期列表Excel
    /// </summary>
    [DisplayName("导出计划完工延期列表Excel")]
    [ApiDescriptionSettings(Name = "ExportPlanEndDelayExcel"), HttpPost]
    public async Task<IActionResult> ExportPlanEndDelayExcel(PlanDelayCount input)
    {
        var delayList = await GetPlanEndDelayOutList(input);
        return await CommonUtil.ExportExcelData(delayList, "计划完工延期明细");
    }

    #endregion

}
